# -*- coding: utf-8 -*-
import sys
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QTableWidgetItem, QFileDialog, QWidget, QMessageBox
import os
import yaml
from regex_content_extrac import *
import threading
import pandas as pd
import time

class ParserSetting:
    def __init__(self):
        self.csv_formats = {}
        self.csv_file_dir = ""
        self.log_dir = ""
    
    def read_setting(self, content):
        self.csv_file_dir = content["csv_file_dir"]
        self.log_dir = content['log_dir']
        for config in content['csv_formats']:
            self.csv_formats[config['csv_name']] = config['csv_format']
    
    def update_log_dir(self, log_dir):
        self.log_dir = log_dir
    
    def update_csv_dir(self, csv_dir):
        self.csv_file_dir = csv_dir
    
    def get_log_dir(self):
        return self.log_dir
    
    def get_csv_dir(self):
        return self.csv_file_dir
    
    def sync_with_table(self, formats):
        self.csv_formats = formats
    
    def update_csv_format(self, csv_format, csv_name):
        if csv_name == "":
            return
        self.csv_formats[csv_name] = csv_format
    
    def delete_csv_formate(self, csv_name):
        if csv_name in self.csv_formats:
            del self.csv_formats[csv_name]
    
    def get_csv_formats(self):
        return self.csv_formats
    
    def get_dict_format(self):
        return {
            "csv_file_dir": self.csv_file_dir,
            "log_dir": self.log_dir,
            "csv_formats": [{"csv_name": key, "csv_format": value} for key, value in self.csv_formats.items()]}

class Log2CsvWidget(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi("log2csv.ui", self)  # 将 your_ui_file.ui 替换成你的 ui 文件名

        self.init_config()
        self.init_parser_setting()
        self.init_connect()
        self.init_ui()

    def init_connect(self):
        self.log_dir_pushButton.clicked.connect(self.logdir_pushButtonCB)
        self.csv_dir_pushButton.clicked.connect(self.csvdir_pushButtonCB)
        self.save_config_pushButton.clicked.connect(self.saveconfig_pushButtonCB)
        self.load_config_pushButton.clicked.connect(self.loadconfig_pushButtonCB)
        # 设置events_table的右键菜单
        self.parser_setting_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)  
        # 在tableWidget中点击右键的触发事件
        self.parser_setting_table.customContextMenuRequested[QtCore.QPoint].connect(self.context_menu_of_events_table)
        self.begin_pushButton.clicked.connect(self.begin_pushButtonCB)
        self.parser_setting_table.cellChanged.connect(self.cell_changedCB)

    def init_config(self):
        self.is_working = False
        self.stop_working = False
        self.parser_setting = ParserSetting()
        # 通过init.yaml文件载入配置文件地址
        self.init_file_path = "init.yaml"
        self.config_file_path = ""
        try:
            with open(self.init_file_path, 'r', encoding='utf-8') as file:
                data = yaml.load(file, Loader=yaml.FullLoader)
                self.set_parser_setting(data['config_file_path'])
                self.config_file_path = data['config_file_path']
        except Exception as e:
            QMessageBox.warning(self, "警告", f"初始化配置文件读取失败: {e}")
        self.current_config_file_label.setText(rf"current_config_file: {self.config_file_path}")
        
    def set_parser_setting(self, config_file_path):
        with open(config_file_path, 'r', encoding='utf-8') as config_file:
            content = yaml.load(config_file, Loader=yaml.FullLoader)
            self.parser_setting.read_setting(content)
    
    def init_parser_setting(self):
        self.log_dir_editor.setText(self.parser_setting.get_log_dir())
        self.csv_dir_editor.setText(self.parser_setting.get_csv_dir())
        self.parser_setting_table.clearContents()
        self.parser_setting_table.setRowCount(0)
        for csv_name, csv_formate in self.parser_setting.get_csv_formats().items():
            row_index = self.parser_setting_table.rowCount()
            self.parser_setting_table.insertRow(row_index)  # 自动加1行
            self.parser_setting_table.setItem(row_index, 1, QTableWidgetItem(csv_name))
            self.parser_setting_table.setItem(row_index, 0, QTableWidgetItem(csv_formate))
        self.parser_setting_table.resizeColumnsToContents()
        self.parser_setting_table.resizeRowsToContents()
    
    def init_ui(self):
        self.file_progress_bar.setValue(0)
        self.total_progress_bar.setValue(0)
        self.total_line_num = 0.0

    def cell_changedCB(self, row, column):
        self.parser_setting_table.resizeColumnsToContents()
        self.parser_setting_table.resizeRowsToContents()

    def logdir_pushButtonCB(self):
        directory = QFileDialog.getExistingDirectory(self, "选择日志所在文件夹")
        if directory:
            self.log_dir_editor.setText(directory)
            self.parser_setting.update_log_dir(directory)
    
    def csvdir_pushButtonCB(self):
        directory = QFileDialog.getExistingDirectory(self, "选择保存csv所在文件夹")
        if directory:
            self.csv_dir_editor.setText(directory)
            self.parser_setting.update_csv_dir(directory)

    def loadconfig_pushButtonCB(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "选择解析配置文件",
            self.config_file_path,
            "所有文件 (*);;文本文件 (*.yaml)"
        )
        if file_path:
            try:
                # init the parser setting
                self.set_parser_setting(file_path)
                self.current_config_file_label.setText(rf"current_config_file: {self.config_file_path}")
                self.config_file_path = file_path
                # init the init file config
                with open(self.init_file_path, 'w', encoding='utf-8') as init_file:
                    yaml.dump({"config_file_path": self.config_file_path}, init_file, allow_unicode=True)
                self.init_parser_setting()
            except Exception as e:
                QMessageBox.critical(self, "错误", f"load config file failure：{str(e)}")

    def saveconfig_pushButtonCB(self):
        # 弹出保存文件对话框
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "保存文件",
            "config.yaml",  # 默认文件名
            "文本文件 (*.yaml);;所有文件 (*)"
        )

        if file_path:
            try:
                self.parser_setting.sync_with_table(self.collect_parser_setting())
                with open(file_path, 'w', encoding='utf-8') as config_file:
                    yaml.dump(self.parser_setting.get_dict_format(), config_file, allow_unicode=True)
                QMessageBox.information(self, "成功", f"文件已保存到：\n{file_path}")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存失败：{str(e)}")

    def context_menu_of_events_table(self, pos):
        """
                用于在tableWidget中点击右键的触发事件，显示右键菜单
            :return:
            """
        pop_menu = QMenu()
        add_new_event = pop_menu.addAction('添加新行')
        del_selected_event = pop_menu.addAction('删除选中行')

        action = pop_menu.exec_(self.parser_setting_table.mapToGlobal(pos))

        if action == add_new_event:  # 如果选中的是添加新的事件
            """
                如果是添加新行，首先必须先将表格的总行数加1，才能进行后续操作，将表格的行数加1之后，再在新加的行内每一列中添加Item
            """
            cur_total_row = self.parser_setting_table.rowCount()
            cur_row = cur_total_row + 1
            self.parser_setting_table.setRowCount(cur_row)  # 将当前的总行数加1
            self.parser_setting_table.setItem(cur_row-1, 0, QTableWidgetItem(""))
            self.parser_setting_table.setItem(cur_row-1, 1, QTableWidgetItem(f"{cur_row}_log.csv"))

        elif action == del_selected_event:
            """
                如果我们选择用.selectedItems()函数来得到当前选中的多行，一定要注意，这个函数返回的是所有选中的item，即返回的item的个数为：选中的行数 * 每一行的item数(我这个代码中有8列，但每行只返回7个item，我也不知道为什么，这一点需要注意一下)。
                每个item都可以通过.indexFromItem(items).row()得到其所在的行号，再通过.removeRow(row_number)删除对应的行。
                所以在删除时并不是所有的item都需要用，因为很显然要删除每一行都只需要一个该行的item即可，如果有多个肯定会报错，所以我才做了如下操作，在固定间隔取一个item，保证每一行中都只有一个item保留下来，这样就能保证每个被选中的行都只被删除一次。
                还有一个需要注意的问题，如果选择多行的话，比如选择的行号为1,2,3,7,9，如果我们从前面开始删除，比如先删除第1行，则剩下的行的行的删除会出现问题，因为第1行删除后，原来的第2行会变成新的第1行，原来的第3行会变成新的第2行，以此类推，这很显然会导致剩余行的删除出现问题。我这里的解决办法是先将要删除的行进行降序排序，从后往前删除则可以保证删除的行不会发生错误。        

            """
            selected_items = self.parser_setting_table.selectedItems()
            if len(selected_items) == 0:  # 说明没有选中任何行
                return
            selected_items = [selected_items[i] for i in range(len(selected_items)-1, -1, -7)]
            # 将选定行的行号降序排序，只有从索引大的行开始删除，才不会出现错误
            for items in selected_items:
                self.parser_setting_table.removeRow(self.parser_setting_table.indexFromItem(items).row())

    def collect_parser_setting(self):
        rows = self.parser_setting_table.rowCount()
        result = {}
        for r in range(rows):
            item0 = self.parser_setting_table.item(r, 0)
            item1 = self.parser_setting_table.item(r, 1)
            csv_format = item0.text().strip() if item0 else ""
            csv_name   = item1.text().strip() if item1 else ""
            # 如果需要，跳过整行都为空的
            if not csv_format and not csv_name:
                continue
            result[csv_name] = csv_format
        return result


    def start_command(self):
        if not os.path.exists(self.parser_setting.get_log_dir()):
            self.is_working = False
            QMessageBox.critical(self, "错误", f"log dir doesn't exist!")
            return
        
        if not os.path.exists(self.parser_setting.get_csv_dir()):
            self.is_working = False
            QMessageBox.critical(self, "错误", f"csv dir doesn't exist!")
            return

        print("directory valite!")
        # 启动新线程执行任务
        if not self.is_working:
            self.start_ui_update_timer()  # 启动 QTimer
            self.parser_thread = threading.Thread(target=self.start_parse, daemon=True).start()
            # self.ui_update_thread = threading.Thread(target=self.start_ui_update, daemon=True).start()
            self.parser_commands = self.collect_parser_setting().items()
        else:
            self.stop_working = True
            print("stop working")

    def start_parse(self):
        print("parser enter")
        self.is_working = True
        self.working_state()
        df = pd.DataFrame()  # 创建空表
        total_start = time.perf_counter()
        csv_frames = {}
        self.log_dir = self.parser_setting.get_log_dir()
        print("stage 1")
        try:
            for dir_path, dir_name, file_names in os.walk(self.parser_setting.get_log_dir()):
                if self.stop_working:
                    break
                if len(file_names) == 0:
                    continue
                self.max_file_nums = len(file_names)
                file_count = 0
                print(f"stage 2 {dir_path}")
                for file_name in sorted(file_names,
                                        key=lambda x: os.path.getmtime(
                                            os.path.join(self.log_dir, x))):
                    if self.stop_working:
                        print("start parser stop parsing in file loop")
                        break
                    print(f"stage 3 {file_name}")
                    file_count = file_count + 1
                    self.current_file_num = file_count
                    total_end = time.perf_counter()

                    if ".gz" in file_name:
                        continue
                    file = os.path.join(dir_path, file_name)
                    num_file = sum([1 for i in open(file, "r")])
                    line_index = 0
                    self.current_file_name = file_name
                    self.total_line_num = num_file
                    with open(os.path.join(dir_path, file_name), 'r', encoding='utf-8') as fh:
                        line_start =  time.perf_counter()
                        for line_str in fh:
                            line_end = time.perf_counter()
                            self.file_costtime = line_end - line_start
                            total_end = time.perf_counter()
                            self.total_costtime = total_end - total_start
                            if self.stop_working:
                                print("start parser stop parsing when read file")
                                break
                            line_index = line_index + 1
                            self.cur_file_progress = line_index
                            for csv_filename, csv_formate in self.parser_commands:
                                if csv_formate == "":
                                    continue
                                result = self.parse_content(csv_formate, line_str)
                                if result == None or len(result) == 0:
                                    continue
                                if csv_filename in csv_frames:
                                    csv_frames[csv_filename].append(result)
                                else:
                                    csv_frames[csv_filename] = []
                                    csv_frames[csv_filename].append(result)
                                break
                # avoid second directory which cause crash
                break
        except Exception as e:
            self.stop_working = True
            QMessageBox.critical(self, "错误", f"parse log file error：{str(e)}")
            
        if self.stop_working:
            print("start_parser stop working!")
            self.stop_working = False
        else:
            try:
                for csv_filename, frames in csv_frames.items():
                    df = pd.DataFrame(frames)
                    csv_file_path = os.path.join(self.parser_setting.get_csv_dir(), csv_filename)
                    self.save_csv(df, csv_file_path)
                    print("save_succes")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"parse log file error：{str(e)}")
        self.is_working = False
        self.edit_state()
        print("parser exit!")
        
    def parse_content(self, template, line_str):
        result = extract_by_template(template, line_str)
        return result
    
    def save_csv(self, data_frames, csv_file_path):
        data_frames.to_csv(csv_file_path, index=False, encoding="utf-8", sep=",")
    
    def working_state(self):
        self.log_parser_qtab.setTabEnabled(0, False)  # 禁用第 index 个 tab
        self.begin_pushButton.setText("stop")
    
    def edit_state(self):
        self.log_parser_qtab.setTabEnabled(0, True)
        self.begin_pushButton.setText("begin")
    
    def begin_pushButtonCB(self):
        self.start_command()
    
    def start_ui_update_timer(self):
        self.ui_timer = QtCore.QTimer()
        self.ui_timer.timeout.connect(self.update_ui)
        self.ui_timer.start(100)  # 每 100ms 更新一次界面

    def update_ui_operateion(self):
        self.file_progress_bar.setValue(0)
        self.file_progress_bar.setValue(0)

        self.total_file_num_label.setText(f"total_file_num: {self.max_file_nums}")
        self.current_file_num_label.setText(f"current_file_num: {self.current_file_num}")
        self.total_progress_bar.setMaximum(self.max_file_nums)
        self.file_progress_bar.setMaximum(self.total_line_num)
        
        percent = 0.0
        if self.max_file_nums > 0:
            percent = self.current_file_num / self.max_file_nums
        self.file_percent_label.setText(f"percent: {percent:.2%}")
        self.total_progress_bar.setValue(self.current_file_num)
        self.total_time_label.setText(f"time_cost: {self.total_costtime:.2f} s")
        
        # file dealing
        self.current_file_name_label.setText(f"current_file_name: {self.current_file_name}")
        self.line_total_num_label.setText(f"total_line_num: {self.total_line_num}")
        self.current_line_num_label.setText(f"current_line_num: {self.cur_file_progress}")

        if self.total_line_num > 0:
            file_percent = self.cur_file_progress / self.total_line_num
        else:
            file_percent = 0.0
        self.current_file_percent_label.setText(f"percent: {file_percent:.2%}")
        self.file_time_cost_label.setText(f"file_time_cost: {self.file_costtime:.3f}")
        self.file_progress_bar.setValue(self.cur_file_progress)

    def update_ui(self):
        self.update_ui_operateion()
        if not self.is_working:
            self.ui_timer.stop()
            print("ui update exit!")
            return
